Isetta programming manual ------------------------- 3-1-2025 started 10-1-2025 updated 11-1-2025 new blitter codes 9-2-2025 debugger function working 12-3-2025 manual published on hackaday.io page Chapter 1 I/O ports Chapter 2 Video system Chapter 3 Blitter Chapter 4 Debugger support Chapter 1 I/O ports -------------------- Just as in the Z80 and 8080, Isetta can input or output a single byte at I/O ports. There are 256 possible ports for input and 256 for output. An I/O instruction is two bytes long: <0xD3> OUT instruction: This will output the byte in ACC to the specified port number <0xDB> IN instruction: This will input a byte from the specified port number and put it in ACC. In Isetta, these IN and OUT instructions are also in the 6502 instruction set, with the same opcodes 0xD3 and 0xDB. Using undefined I/O port numbers will, in the current version, most likely result in a crash. I/O ports - general ------------------- port_in_config = 0xE4; // INPUT this tells what the HW configuration is. For the current version, always zero. port_in_microcode_year = 0xE6 ; // INPUT reads the microcode date from microcode flash (reads from flash page 6). port_in_microcode_month = 0xE7 ; port_in_microcode_day = 0xE8 ; port_out_isa_6502 = 0x0A; // OUTPUT switch instruction set to 6502. Preserves Accumulator and PC. port_out_isa_Z80 = 0x0B; // OUTPUT switch instruction set to Z80. Preserves Accumulator and PC. Instead of switching instruction set with writing an arbitrary value to the above ports, it is also possible to write to "port_io_reg_isa" with the ISA number in ACC. Reading from this port gives the current ISA. port_io_reg_isa = 0x10; Supported values for port_io_reg_isa are: const ISA_6502 = 0x38; const ISA_Z80 = 0x39; const ISA_BLIT = 0x3a; // Blitter I/O ports - memory banks and bankswitching ------------------------------------------ There are 8 memory banks (of 64kByte each). Each bank has 4 blocks of 16 kByte. In this section, the hardware bank numbers are used. Software may define bank numbers different. After startup, your Z80 or 6502 program will normally run in HW bank 3. Use of the memory banks: HW bank 0 system data, Hires screen buffer, upper half HW bank 1 system data, upper part may be free to use (not fully defined yet) HW bank 2 system data, Hires screen buffer, lower half HW bank 3 Main Z80/6502 bank HW bank 4 free to use HW bank 5 free to use HW bank 6 free to use HW bank 7 free to use Transfer a single byte to/from another bank: port_io_bank0 = 0xF0 // IN/OUT read/write byte (in ACC) from/to memory location (in HL) in HW bank0. port_io_bank1 = 0xF1 // same for HW bank1 port_io_bank2 = 0xF2 // same for HW bank2 port_io_bank3 = 0xF3 // same for HW bank3 For bankswitching, the "out (c),reg" instruction is used. "reg" can be any register a,b,c,d,e,h,l. For the moment, this is only available in the Z80 instruction set. port_out_system = 0x18; // OUTPUT default is 0x00. Set it to 0x40 to enable bankswitching. OUTPUT out (c),reg: set bank configuration according to register "reg": ( C must be 11xx xxxx) reg = 11000000 ; // The Z80/6502 only sees HW bank 3 (this is the default). (C0) reg = 11BBB010 ; // The Z80/6502 only sees the memory bank according to BBB (see table). (C2) reg = 11BBB1LL ; // The Z80/6502 only sees bank 3, but in the range 0x4000 - 0x7FFF it sees // block number LL from the memory bank selected with BBB (see table). (C4-7) reg = 11BBB001 ; // The Z80/6502 only sees bank 3, but in the range 0xC000 - 0xFFFF it sees // block number 3 from the memory bank selected with BBB (see table). But it can not // execute from that block. (C1) table: BBB LL 000 HW bank 4 00 block 0 (0x0000-0x3FFF) 001 HW bank 5 01 block 1 (0x4000-0x7FFF) 010 HW bank 6 10 block 2 (0x8000-0xBFFF) 011 HW bank 7 11 block 3 (0xC000-0xFFFF) 100 HW bank 0 101 HW bank 1 110 HW bank 2 111 HW bank 3 todo: - output to set HL register (6502) - have bankswitch instruction for 6502 I/O ports - Character I/O and Mouse ----------------------------------- port_in_scancode = 0x31; // IN read PS/2 keyboard scancode (bits reversed) or byte from RPi. // This input reads from a 256 byte buffer. // Returns ACC=zero if the buffer is empty. port_out_tx = 0x31; // OUT sends a byte to the RPi. RPi has to provide the clock. // if ACC=0 after this instruction, the buffer was full and the OUT must be retried. port_in_mouse = 0x32; // IN receive a byte from the PS/2 mouse (bits reversed). // This input reads from a 256 byte buffer. // Returns Z=1 if the buffer was empty. Information about the PS/2 protocol can be found here: https://www.eecg.utoronto.ca/~jayar/ece241_08F/AudioVideoCores/ps2/ps2.html The mouse must be enabled by giving it a 0xF4 command, Isetta does that always at start-up. Several PS/2 keyboards must also be enabled, Isetta does that also at startup. I/O ports - SPI functions ------------------------- port_spi1_start = 0x32; // OUT start transaction on SPI-1 port_spi2_start = 0x33; // OUT start transaction on SPI-2 port_spi_out = 0x37; // OUT send byte (in ACC) to SPI device port_spi_in = 0x3A; // IN read byte from SPI device to ACC port_spi_idle = 0x3F; // OUT stop transaction port_spi_state = 0x3C; // IN read status byte from SPI. Sets Z flag if SPI active. Note 1) While a SPI transaction is in progress, instructions that write the N flag (negative/sign) should not be used. Note 2) spi1_start has the same port number as in_mouse. But since the first one is output and the other one input, there is no conflict. Note 3) While a SPI transaction is in progress, the RST #38 interrupts are disabled (by microcode). I/O ports - Sound ----------------- port_out_sound = 0x30; // OUT output a byte to the sound_output/general output. Effective at next scanline interrupt. I/O ports - ZX Spectrum specific --------------------------------- port_io_zx_spectrum = 0xFE // IN/OUT port_io_zx_joystick = 0x1F // IN reserved for joystick. Unused. port_io_zx_128 = 0xFD // reserved for OUT, ZX_Spectrum 128 paging system Chapter 2 Video system ----------------------- The video system uses a "frame table" that describes what should happen at every scanline. There are 525 scanlines in a normal VGA screen (400 or 480 visible), so the table is 525 bytes long. The microcode part that writes to the screen will read a action-code byte from this table at the start of each scanline, and that byte determines the action that the microcode will execute. Frame table location: 0x0300 in HW bank 1. (but it can be moved to another position). possible actioncodes: v_prepare equ $24 ; At very first frame table location. Initializes for the visual scanlines that follow. v_end equ $25 ; This has to follow the last visible scanline v_blank equ $26 ; This is a line in blanking period. Almost all time in these lines is used for Z80/6502 processing. v_wait equ $27 ; This must be in the line just before changing the vertical sync signal v_act_vsync equ $2A ; At this line, vertical sync is activated v_deact_vsync equ $2B ; At this line, vertical sync is de-activated v_repeat equ $2C ; At the end of the frame table, causes a jump back to the start of the frame table And here are the actioncodes that define the video mode at each visible scanline: v_hires equ $2D ; This causes a line of high-resolution video to be written to the screen v_spectrum equ $0D ; This causes a line of ZX-spectrum video to be written to the screen v_pix_320 equ $34 ; This causes a line of 320 pixels to be written to the screen In general, the codes for the video mode also refer to a video line and attribute line: (example for line n) 0x0300 + n (bank1) ; this byte has the action code 0x0300 + n (bank0) ; this byte has the LSB of the video line data address 0x0300 + n (bank2) ; this byte has the MSB of the video line data address 0x0600 + n (bank1) ; this byte has, for ZX spectrum mode, the MSB of the video attribute line. Hires video mode ---------------- The hires mode has a resolution of 640 x 400 pixels, with 16 colors for every pixel. For the hires mode, the video line is 320 bytes long. Each byte has a first pixel in the high nibble, and a second pixel in the low nibble, so the video line is 640 pixels long. The screen is divided in the upper half and the lower half. For the upper half, the video lines are in HW bank 0 starting at 0x0600, up to 0xFFFF (200 upper half lines). For the lower half, the video lines are in HW bank 2 starting at 0x0600, up to 0xFFFF (200 lower half lines). So, the video data is almost 128 kBytes. Since the video line address is 17 bits, it is stored in a special way in the table. Supposing that the address is always even, it is shifted one bit to the right. The upper bit is then filled with a "0" if it is in the upper half and a "1" if it is in the lower half of the screen. The pixel color in the nibble is defined as "DRGB", where R,G,B bits define if the R,G,B components are each on or off. The "D" bit will, if set to 1, result in a dark version of the specified color. Note that, since the start address of each video line is in the frame table, scrolling up or down can be very fast, since only the start addresses have to be changed in the table. Horizontal scrolling can also be very quick. 320 pixel video mode -------------------- This mode has a resolution of 320 x 200 pixels, with 64 colors for every pixel. This is not tested yet. ZX Spectrum video mode ---------------------- The ZX spectrum video mode has a resolution of 256 x 192 pixels with 2 colors. For each block of 8 x 8 pixels there is an attribute byte, that defines the 2 possible colors for these 64 pixels. Each video line is located in the normal addressing range of the processor (HW bank 3), starting at 0x4000 (up to 0x57FF). Each line is 32 bytes long, with 8 pixels per byte this defines the 256 pixels of a line. The starting address of a video line is in the frame table as described before. The attribute bytes, also 32 bytes per line, are also in the normal addressing range of the processor, starting at 0x5800 (up to 0x5AFF). The LSB of the address of this attribute byte is the same as the LSB of the pixel data. The MSB of the attribute byte is at location "0x0600 + n (bank1)" as described above. The layout of the video lines in memory is not straightforward, you can find it here: (also describes how color is encoded) http://www.breakintoprogram.co.uk/hardware/computers/zx-spectrum/screen-memory-layout The program "ZXOS.BIN" will set-up the frame table and other things to make Isetta run ZX Spectrum programs. Only the flashing of video data is not implemented (yet?). Chapter 3 Blitter ------------------ The Blitter is a microcoded unit that is specialized in writing to rectangular sections of the Hires screen. To use this, you first setup the parameters, using special OUTPUT ports. After that, you start the blitter by writing the action code to the P_BLITCTRL output port. Most commands require three kinds of parameters: (most parameters are 16 bits) - source base address, coordinates, line-length - screen coordinates - rectangle size A few commands use FILL1 and FILL2 that both specify a color. Blitter Source parameters port numbers (SRC) -------------------------------------------- The source parameters can reere to a position in memory, or to a position on the screen. P_BLITSRCX_L equ #90 ; src_x_l X-coordinate (in pixels) P_BLITSRCX_H equ #91 ; src_x_h P_BLITSRCY_L equ #92 ; src_y_l Y-coordinate (in pixels) P_BLITSRCY_H equ #93 ; src_y_h P_BLITSRCA_L equ #97 ; src_a_l Base address (24 bits) P_BLITSRCA_H equ #98 ; src_a_h P_BLITSRCA_U equ #9A ; src_a_u (upper byte) P_BLITSRCL_L equ #9B ; src_l_l line length (bytes) P_BLITSRCL_H equ #9F ; src_l_h When the source is memory, Y-coordinate should be zero. The base address should be set, and src_a_u should be a value 0 - 7, where the value 0 is HW bank3, value 1 is HW bank4, etc (wrapping around). The source rectangle should be fully inside a single HW bank. The line length gives the number of bytes to be added to go to the next source line. For writing TEXT from a 8x8 font (where each byte represents a character row), the line length should be set to 1. When the source is a screen address, X and Y give the pixel coordinates, and the 24-bit address should be 0x800600 (The upper bit indicates that a screen address is used). The HW bank is selected automatically (screen rectangle can be partialy on upper and lower screen half). The line length should be 320 (number of bytes to go to next line) When the action is D_BMFILL or D_BMFXOR, the source parameters are not needed, but src_x_l must be set to 0. Blitter Screen parameters port numbers (DST) -------------------------------------------- P_BLITDSTX_L equ #A0 ; dst_x_l X-coordinate (in pixels) P_BLITDSTX_H equ #A1 ; dst_x_h P_BLITDSTY_L equ #A2 ; dst_y_l Y-coordinate (in pixels) P_BLITDSTY_H equ #A3 ; dst_y_h P_BLITDSTA_L equ #00 ; unused base address P_BLITDSTA_H equ #00 ; unused P_BLITDSTA_U equ #00 ; unused P_BLITDSTL_L equ #00 ; unused line length P_BLITDSTL_H equ #00 ; unused This is always a position on the Hires screen. The upper left pixel has coordinates (0,0). Screen contents is 4bpp (bits per pixel). Blitter Size parameters port numbers ------------------------------------ P_BLITSIZX_L equ #B0 ; size_x_l X-size in pixels P_BLITSIZX_H equ #B1 ; size_x_h P_BLITSIZY_L equ #B2 ; size_y_l Y-size in pixels P_BLITSIZY_H equ #B3 ; size_y_h Blitter Fill parameters port numbers ------------------------------------ P_BLITFILL1 equ #78 ; fill1 (4 bits) (also text foreground color) P_BLITFILL2 equ #79 ; fill2 (4 bits) (also text background color) Blitter Starting action ----------------------- There are several actions that the blitter can perform, they are started by writing the action code to the control port. The blitter will then take control, and after the operation is completed, control is given back to the Z80/6502 processor. P_BLITCTRL equ #EA ; port number for blitter control port Blitter Action codes -------------------- D_BMCOPY equ #00 ; COPY source to screen D_BMSKIP equ #04 ; Copy source to screen, except where source has color 0 (transparent) D_BMSAVE equ #01 ; Save from screen to source memory address D_BMTEXT equ #09 ; TEXT write a character from a font map to the screen. Font map is 1bpp. D_BMFILL equ #2D ; Fill a screen rectangle, alternating colors FILL1 and FILL2 D_BMFXOR equ #2E ; Fill a screen rectangle using XOR operator, alternating colors FILL1 and FILL2 The following modifier codes can be added to the action code: D_BLITBACKX equ #0B ; Only for COPY. Action is performed right-to-left. (not yet possible) D_BLITBACKY equ #80 ; Only for COPY. Action is performed bottom-to-top. (not yet possible) D_BLITP14 equ #00 ; Only used with TEXT. Converts 1bpp to 4 bpp (bits per pixel) D_BLITP24 equ #02 ; Only used with COPY or SKIP. Converts 2bpp to 4 bpp D_BLITP44 equ #00 ; Default, screen and source are both 4bpp After the action the blitter will add size_x_l to dst_x. So, after writing a character it is not needed to set the screen-x-coordinate to the next position. For 2bpp to 4bpp conversion: - 00 converts to 0000 (black) - 01 converts to FILL2 - 10 converts to FILL1 - 11 converts to FILL1 XOR FILL2 NOTE (16-2-2025) The current implementation does give wrong result for mis-aligned (by one pixel) source and screen. Operation SKIP is not yet implemented and will give same result as COPY. NOTE (3-1-2025) The blitter doesn't work for 6502 yet. NOTE The blitter only works if the bankswitching system is enabled. Chapter 4 Debugger support --------------------------- Isetta has support for a debugger. The programmer has to provide the debugger code (Z80). The debugger code must be somewhere in the range 0x8000 - 0xFFFF. Let's call the MSB of the debugger code 0xMM When the debugger is switched on, the following will happen: - at each CALL, the debugger code will be called at address 0xMM20. - at each RET, the debugger code will be called at address 0xMM28. The debugger can be switched ON by writing 0xMM to the debugger control port: port_out_dbg = 0x1A; It can be switched OFF by writing a 0x00 value to this port. NOTE A conditional CALL or RET will not trigger the debugger if the condition is not satisfied. A RETI will not trigger the debugger. NOTE The debugger support is only for Z80 code. Debugger example code for RET instructions ------------------------------------------ We start with the RET instructions, these are simplest. The debugger must define a location where the PC is stored, let's use 0x1234 as example. In this example, we will place our debugging code starting at 0xD000. The first thing in the debugging code, is to store the PC to 0x1234. After encountering the RET instruction, the microcode will save the PC in two internal registers (reg_th and reg_tl). These registers can also be used by scanline interrupts, but between the RET instruction and the first debugging instruction, scanline interrupts are blocked. The next thing is obtaining the return address, and write it into the JP instruction at the end. The next thing to do, is disable debugging. We don't want that a CALL or RET inside the debugger again calls the debugger. We need the ACC, so we first save it to stack with a push. addr instruction ---- ----------- D028 ED 7F 34 12 ; store saved PC at 0x1234 E3 ex (sp),hl ; get return address, while saving HL 22 .. .. ld (dbg_ra+1),hl ; store return address in the JP instruction E1 pop hl ; restore HL F5 push AF AF xor A D3 1A out (0x1A),a ; debugging off ; here you can place code for displaying registers, return address, etc. ; it can also display the saved PC (available at 0x1234) push hl ld hl,(0x1234) ; get saved pc. (This is the address after the RET) dec hl ; can now display HL (address of the RET instruction) pop hl ; after this code, we must switch the debugger ON again ; and perform the actual RET instruction 3E D0 ld a,0xD0 ; MSB of the debugging code D3 1A out (0x1A),a ; debugging ON again ED 6B ei2 F1 pop AF dbg_ra:C3 .. .. jp xxxx ; self-modifying code NOTE If you use a RST #38 interrupt, debugging mode should be switched OFF at the beginning of the interrupt code, and switched ON again at the end, otherwise your debugger will also show every call and ret that occurs during the interrupt. Note that at the end of the interrupt, you should not use a RET instruction (that would immediately trigger a new debugger call). But you can use RETI. NOTE The interrupt system has a second set of enable/disable instructions, called EI2 and DI2. This set can be used independently from the normal EI and DI. Interrupts can only occur if both EI and EI2 are valid. The ED 7F instruction, that stores the saved PC, will also do a DI2. DI2 instruction: ED 63 EI2 instruction: ED 6B The EI2/DI2 set is intended to be used where you want to disable interrupts, but you don't know if interrupts were already disabled with DI. NOTE Scanline interrupts are not disabled by the DI or DI2 instruction. But scanline interrupts do not change the Z80 state (They don't use the stack or any other Z80 registers). Debugger example code for CALL instructions ------------------------------------------- This is similar to the RET code. addr instruction ---- ----------- D020 ED 7F 34 12 ; store saved PC at 0x1234 18 .. JR dbg_1 ; jump over the RET code at 0xD028 dbg_1: F5 push AF AF xor A D3 1A out (0x1A),a ; debugging off ; here you can place code for displaying registers, call address, etc. ; it can also display the saved PC (available at 0x1234) push hl push de ld hl,(0x1234) ; get saved pc. (This is the address after the call) dec hl ld d,(hl) ; msb of call addr dec hl ld e,(hl) ; lsb of call addr dec hl ld (dbg_ca+1),de ; store called address (must ALWAYS be done) ; can now display HL (address of the CALL instruction) ; and display DE (destination of the CALL) pop de pop hl ; after this code, we must switch the debugger ON again ; and perform the actual CALL instruction 3E D0 ld a,0xD0 ; MSB of the debugging code D3 1A out (0x1A),a ; debugging ON again F1 pop AF push hl ; save HL 2A 34 12 ld hl,(0x1234) ; get saved pc. This is the return address. E3 ex (sp),hl ; put return address on stack , while restoring HL ED 6B ei2 dbg_ca:C3 .. .. jp xxxx ; self-modifying code. JP to the destination address end. ----